# 这是预处理数据的函数
from lxml import etree
import scrapy
import requests
import re

# clean_text 函数：接收一个字符串、列表或 etree._Element 类型，
# 返回对应类型的清理结果,传入什么格式,返回什么格式。
# 用于清理 xpath 结果，去除首尾空白字符


def clean_text(text):
    if text is None:
        return None

    # 如果是列表，递归处理每个元素
    if isinstance(text, list):
        return [clean_text(item) for item in text]

    # 如果是etree元素，提取文本并清理
    if isinstance(text, etree._Element):
        return text.xpath('string(.)').strip()

    # 处理字符串和其他类型
    return str(text).strip()


# 这个函数用来把列表中的字符串取交集，即找到所有字符串的公共前缀
def public_str(list_str):
    """
    从列表中找到所有字符串的公共前缀（保留原始头部，仅删除尾部）
    适用于 XPath、URL、路径等结构化字符串。
    """
    if not isinstance(list_str, list) or not list_str:
        raise ValueError("列表为空或不是列表")

    r_str = list_str[0]
    if not isinstance(r_str, str):
        raise ValueError("循环列表中的元素不能嵌套，必须是字符串")

    l_str = r_str.split("/")

    for _str in list_str:
        while not _str.startswith(r_str):
            if not l_str:
                return ""  # 没有公共前缀了

            pop_str = "/" + l_str.pop()  # 删除最后一段
            if r_str.endswith(pop_str):
                r_str = r_str.removesuffix(pop_str)
            else:
                # 如果末尾不是精确段（防止不规范 XPath）
                r_str = "/".join(l_str)

            if not r_str:
                return ""
    return r_str


# 定义HTML类型转换装饰器
def html_type(func):
    def wrapper(html, *args, **kwargs):
        html_type = ""  # 定义html类型变量
        # 处理Scrapy Response
        if isinstance(html, scrapy.http.Response):
            html = scrapy.Selector(response=html)
            html_type = "scrapy.http.Response"

        # 判断是否是Scrapy Selector
        if isinstance(html, scrapy.Selector):
            # 这种情况 html 就不转换了。
            html_type = "scrapy.Selector"

        # 处理requests Response
        if isinstance(html, requests.Response):
            encoding = kwargs.get('encoding', 'utf-8')
            html.encoding = encoding
            html = etree.HTML(html.text)
            html_type = "requests.Response"

        # 处理字符串转etree
        if not html_type and not isinstance(html, etree._Element):
            if not isinstance(html, str):
                raise TypeError(f"无法处理 html 类型: {type(html).__name__}")
            html = etree.HTML(html)
            html_type = "str"

        # 调用原函数，将html_type作为参数传递
        return func(html, *args, html_type=html_type, **kwargs)

    return wrapper

# 这是提取父节点和子节点xpath的函数
# 接收一个html字符串或etree._Element类型，一个xpath字符串列表或字典，
# 返回父节点和子节点xpath字符串列表或字典。


def father_son(html, xpath_strs):
    if isinstance(xpath_strs, list):
        father_xpath = public_str(xpath_strs).rstrip("/")
        son_xpath = [f".{v.removeprefix(father_xpath)}" for v in xpath_strs]
        father_htmls = run_xpath(html, father_xpath)
        return father_htmls, son_xpath
    elif isinstance(xpath_strs, dict):

        father_xpath = public_str(list(xpath_strs.values())).rstrip("/")
        son_xpath = {
            k: f".{v.removeprefix(father_xpath)}" for k, v in xpath_strs.items()}

        father_htmls = run_xpath(html, father_xpath)
        return father_htmls, son_xpath
    else:
        return None, None


# run_xpath 函数：接收一个 html 字符串或 etree._Element 类型，一个 xpath 字符串，
# 一个布尔值，返回 xpath 结果列表。
@html_type  # 装饰器，统一处理html类型
def run_xpath(html, path, html_type='str'):
    match = re.fullmatch(r"(?P<path>.*?)(?:~(?P<flag>\d+))?", path)
    xpath_expr = match["path"] if match else path
    flag = int(match["flag"]) if match and match["flag"] else None
    try:
        if not isinstance(path, str) or not path or path == ".":
            raise ValueError("最终解析的 xpath 表达式必须是字符串且不能为空")
        # 统一执行一次 xpath
        nodes = html.xpath(xpath_expr)
        if not nodes:
            return "" if flag == 0 else []

        # Scrapy 的 Selector 需要用 .getall() 转字符串
        if html_type == "scrapy.Selector":
            if flag == 0:
                return nodes[0].get() if nodes else ""
            elif flag:
                return [n.get() for n in nodes[:flag]]
            else:
                return nodes.getall()

        # lxml 的节点
        if flag == 0:
            return nodes[0] if nodes else ""
        elif flag:
            return nodes[:flag]
        return nodes
    except Exception as e:
        print(f"xpath 解析失败: {e}")
        return "" if flag == 0 else []
